package phases

import (
	"context"
	"fmt"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	bkevalidate "gopkg.openfuyao.cn/bkecommon/cluster/validation"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	ctrl "sigs.k8s.io/controller-runtime"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/mergecluster"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe/phaseutil"
	bkessh "gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/remote"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/clusterutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/condition"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/constant"
)

const (
	EnsureBKEAgentName confv1beta1.BKEClusterPhase = "EnsureBKEAgent"
	// deployCertDir 是远端 registry 的证书目录
	deployCertDir = "/etc/openFuyao"
	// deployCACrt 是 本地 CA 证书文件全路径
	deployCACrt = deployCertDir + "/ca.crt"
)

type EnsureBKEAgent struct {
	phaseframe.BasePhase
	localKubeConfig []byte
	needPushNodes   bkenode.Nodes
}

func NewEnsureBKEAgent(ctx *phaseframe.PhaseContext) phaseframe.Phase {
	base := phaseframe.NewBasePhase(ctx, EnsureBKEAgentName)
	return &EnsureBKEAgent{BasePhase: base}
}

func (e *EnsureBKEAgent) Execute() (_ ctrl.Result, err error) {
	_, _, _, _, log := e.Ctx.Untie()

	if err := e.loadLocalKubeConfig(); err != nil {
		log.Error(constant.BKEAgentNotReadyReason, "Failed to load local kube config, err: %v", err)
		return ctrl.Result{}, err
	}

	// get need push agent nodes
	if err := e.getNeedPushNodes(); err != nil {
		log.Error(constant.BKEAgentNotReadyReason, "Failed to get need push nodes, err: %v", err)
		return ctrl.Result{}, err
	}

	if e.needPushNodes == nil || len(e.needPushNodes) == 0 {
		log.Info(constant.BKEAgentNotReadyReason, "No more nodes need to push BKEAgent")
		return ctrl.Result{}, nil
	}

	// start push
	log.Info(constant.BKEAgentNotReadyReason, "Push BKEAgent will take some time, please wait")

	if err := e.pushAgent(); err != nil {
		log.Warn(constant.BKEAgentNotReadyReason, "Failed to push agent, err: %v", err)
		return ctrl.Result{}, err
	}

	log.Info(constant.BKEAgentNotReadyReason, "Collect node hostname if it is not set in the BKECluster resource")
	if err := e.pingAgent(); err != nil {
		log.Warn(constant.BKEAgentNotReadyReason, "Failed to ping agent, err: %v", err)
		return ctrl.Result{}, err
	}
	return ctrl.Result{}, nil
}

func (e *EnsureBKEAgent) NeedExecute(old *bkev1beta1.BKECluster, new *bkev1beta1.BKECluster) (needExecute bool) {
	if !e.BasePhase.DefaultNeedExecute(old, new) {
		return false
	}

	needExecute = phaseutil.GetNeedPushAgentNodes(new).Length() > 0
	if needExecute {
		e.SetStatus(bkev1beta1.PhaseWaiting)
	}
	return needExecute
}

func (e *EnsureBKEAgent) loadLocalKubeConfig() error {
	ctx, c, _, _, log := e.Ctx.Untie()
	// getLocalClusterKubeconfig
	localKubeConfig, err := phaseutil.GetLocalKubeConfig(ctx, c)
	if err != nil {
		if apierrors.IsNotFound(err) {
			log.Error(constant.BKEAgentNotReadyReason, "Local kubeconfig secret not found")
			return errors.New("Local kubeconfig secret not found")
		}
		log.Error(constant.BKEAgentNotReadyReason, "Failed to get local kubeconfig secret, err：%v", err)
		return errors.Errorf("Failed to get local kubeconfig secret, err：%v", err)
	}
	e.localKubeConfig = localKubeConfig
	return nil
}

func (e *EnsureBKEAgent) getNeedPushNodes() error {
	nodes := phaseutil.GetNeedPushAgentNodes(e.Ctx.BKECluster)
	if len(nodes) == 0 {
		return nil
	}
	// set node state
	for _, node := range nodes {
		e.Ctx.BKECluster.SetNodeStateWithMessage(node.IP, bkev1beta1.NodeInitializing, "Pushing bkeagent")
	}
	if err := mergecluster.SyncStatusUntilComplete(e.Ctx.Client, e.Ctx.BKECluster); err != nil {
		return err
	}
	e.needPushNodes = nodes
	return nil
}

// pushAgent push bkeagent to Nodes
func (e *EnsureBKEAgent) pushAgent() error {
	ctx, c, bkeCluster, _, log := e.Ctx.Untie()
	nodesInfoToPrint := []string{}
	for _, node := range e.needPushNodes {
		nodesInfoToPrint = append(nodesInfoToPrint, phaseutil.NodeInfo(node))
	}
	log.Info(constant.BKEAgentNotReadyReason, "Start push BKEAgent to node(s) %v", nodesInfoToPrint)
	hosts := phaseutil.NodeToRemoteHost(e.needPushNodes)
	ntpServer := bkeCluster.Spec.ClusterConfig.Cluster.NTPServer

	// generate bkeagent.service
	dirName, err := os.MkdirTemp(os.TempDir(), e.Ctx.BKECluster.Name)
	if err != nil {
		return errors.Errorf("Failed to create temp dir, err: %v", err)
	}
	defer os.RemoveAll(dirName)

	file, err := os.ReadFile("/bkeagent.service.tmpl")
	if err != nil {
		return errors.Errorf("Failed to read /bkeagent.service.tmpl, err: %v", err)
	}
	ntpServer = strings.ReplaceAll(string(file), "--ntpserver=", fmt.Sprintf("--ntpserver=%s", ntpServer))

	servicePath := filepath.Join(dirName, "bkeagent.service")
	if err = os.WriteFile(servicePath, []byte(ntpServer), 0644); err != nil {
		return errors.Errorf("Failed to create bkeagent.service, err: %v", err)
	}

	failedNodeIPs := []string{}
	failedNodesWithErr, err := e.sshPushAgent(ctx, hosts, e.localKubeConfig, servicePath)
	for nodeIP, errInfos := range failedNodesWithErr {
		failedNodeIPs = append(failedNodeIPs, nodeIP)
		bkeCluster.SetNodeStateWithMessage(nodeIP, bkev1beta1.NodeInitFailed, fmt.Sprintf("Failed push bkeagent, err: %v", errInfos))
		log.Error(constant.BKEAgentNotReadyReason, errInfos.Error())
	}

	// 没有一个节点成功返回
	if len(failedNodeIPs) == e.needPushNodes.Length() {
		//及时更新node 失败状态
		if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster); err != nil {
			return err
		}
		return errors.Errorf("Failed to push agent to nodes %v", nodesInfoToPrint)
	}

	// 给成功的节点添加标记,避免再次push
	for _, node := range e.needPushNodes {
		if utils.ContainsString(failedNodeIPs, node.IP) {
			continue
		}
		bkeCluster.MarkNodeStateFlag(node.IP, bkev1beta1.NodeAgentPushedFlag)
	}

	if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster); err != nil {
		return err
	}

	// 有master节点没有成功返回
	// todo现在是忽略了master节点加入的情况
	for _, nodeIP := range failedNodeIPs {
		if e.needPushNodes.Master().Filter(bkenode.FilterOptions{"IP": nodeIP}).Length() != 0 {
			log.Warn(constant.BKEAgentNotReadyReason, "Push agent to master node failed, process exit")
			return errors.Errorf("Push agent to master node failed, process exit")
		}
	}

	if bkeCluster.Status.ClusterHealthState == bkev1beta1.Deploying && len(failedNodeIPs) > 0 {
		return errors.Errorf("At Deploying state, all nodes need push success, retry later. failed nodes: %v", failedNodeIPs)
	}

	return nil
}

func (e *EnsureBKEAgent) sshPushAgent(ctx context.Context, hosts bkessh.Hosts, localKubeConfig []byte, servicePath string) (map[string]error, error) {

	pushAgentErrs := map[string]error{}

	multiCli := bkessh.NewMultiCli(ctx)
	defer multiCli.Close()
	multiCli.SetLogger(e.Ctx.Log.NormalLogger)

	regisErrs := multiCli.RegisterHosts(hosts)
	for hostIP, err := range regisErrs {
		pushAgentErrs[hostIP] = err
	}
	if len(multiCli.AvailableHosts()) == 0 {
		return pushAgentErrs, errors.New("No available hosts to push")
	}

	// 检查命令获取目标机器系统架构
	unknownArchErrs := multiCli.RegisterHostsInfo()
	for nodeIP, err := range unknownArchErrs {
		e.Ctx.Log.Warn("HostArchUnknown", "node %s, err: %v", nodeIP, "unknown arch")
		pushAgentErrs[nodeIP] = err
	}

	if len(multiCli.AvailableHosts()) == 0 {
		return pushAgentErrs, errors.New("No available hosts to push")
	}

	//前置命令 使用sudo修改文件夹权限
	preCommand := bkessh.Command{
		Cmds: bkessh.Commands{
			"chmod 777 /usr/local/bin/",
			"chmod 777 /etc/systemd/system/",
			// 忽略输出
			"systemctl stop bkeagent 2>&1 >/dev/null || true",
			"systemctl disable bkeagent 2>&1 >/dev/null || true",
			"systemctl daemon-reload 2>&1 >/dev/null || true",
			"rm -rf /usr/local/bin/bkeagent* 2>&1 >/dev/null || true",
			"rm -f /etc/systemd/system/bkeagent.service 2>&1 >/dev/null || true",
			"rm -rf /etc/bkeagent 2>&1 >/dev/null || true",
		},
	}

	stdErrs, _ := multiCli.Run(preCommand)
	for nodeIP, serrs := range stdErrs.Out() {
		e.Ctx.Log.Warn("PreCommandFailed", "node %s, err: %v", nodeIP, serrs.String())
		pushAgentErrs[nodeIP] = errors.Errorf("Failed to push BKEAgent to node %s, PreCommandFailed err: %s", nodeIP, serrs.String())
		multiCli.RemoveHost(nodeIP)
	}

	if len(multiCli.AvailableHosts()) == 0 {
		return pushAgentErrs, errors.New("No available hosts to push")
	}

	// 1. 初始化文件上传列表，先添加必须的服务文件
	fileUpList := []bkessh.File{
		{Src: servicePath, Dst: "/etc/systemd/system"}, // 服务文件（假设必存在）
	}

	// 2. 检查证书文件（deployCACrt）是否存在
	// 使用os.Stat检查文件是否存在
	if _, err := os.Stat(deployCACrt); err == nil {
		// 文件存在：添加到上传列表
		fileUpList = append(fileUpList, bkessh.File{
			Src: deployCACrt,
			Dst: deployCertDir,
		})
		log.Infof("证书文件 %s 存在，将上传到 %s", deployCACrt, deployCertDir)
	} else if os.IsNotExist(err) {
		// 文件不存在：跳过，不添加
		log.Infof("证书文件 %s 不存在，不进行上传", deployCACrt)
	} else {
		// 其他错误（如权限问题）：记录警告，不添加
		log.Warnf("检查证书文件 %s 时出错：%v，不进行上传", deployCACrt, err)
	}

	// push and start bkeagent
	startCommand := bkessh.Command{
		FileUp: fileUpList, // 动态列表：仅包含存在的文件
		Cmds: bkessh.Commands{
			//在要推送的 节点上创建文件夹，且权限正确
			fmt.Sprintf("mkdir -p -m 755 %s ", deployCertDir),
			"mv -f /usr/local/bin/bkeagent_* /usr/local/bin/bkeagent",
			"mkdir -p -m 777 /etc/bkeagent",
			"chmod +x /usr/local/bin/bkeagent",
			// nodeIP and localKubeConfig needs pre-exist before start bkeagent

			fmt.Sprintf("echo -e %q > /etc/bkeagent/config", localKubeConfig),
			"systemctl daemon-reload 2>&1 >/dev/null",
			"systemctl enable bkeagent 2>&1 >/dev/null",
			"systemctl restart bkeagent 2>&1 >/dev/null",
		},
	}

	multiCli.RegisterHostsCustomCmdFunc(phaseutil.HostCustomCmdFunc)

	stdErrs, _ = multiCli.Run(startCommand)
	// ignore systemctl enable stderr
	for nodeIP, serrs := range stdErrs.Out() {
		e.Ctx.Log.Warn("StartBKEAgentFailed", "node %s, err: %v", nodeIP, serrs.String())
		//pushAgentErrs[nodeIP] = errors.Errorf("Failed to push BKEAgent to node %s, StartBKEAgentFailed err: %s", nodeIP, serrs.String())

		tmpErrs := []string{}
		for _, err := range serrs {
			if strings.Contains(err.Out, "Created symlink") || strings.Contains(err.Out, "Failed to execute operation: File exists") {
				continue
			}
			tmpErrs = append(tmpErrs, err.Out)
		}
		if len(tmpErrs) == 0 {
			delete(pushAgentErrs, nodeIP)
			continue
		}

		pushAgentErrs[nodeIP] = errors.New(strings.Join(tmpErrs, ";"))
		multiCli.RemoveHost(nodeIP)
	}

	if len(multiCli.AvailableHosts()) == 0 {
		return pushAgentErrs, errors.New("No available hosts to push")
	}

	// 清除额外的命令
	multiCli.RemoveHostsCustomCmdFunc()

	//后置命令 使用sudo恢复文件夹权限
	postCommand := bkessh.Command{
		Cmds: bkessh.Commands{
			"chmod 755 /usr/local/bin/",
			"chmod 755 /etc/systemd/system/",
		},
	}

	stdErrs, _ = multiCli.Run(postCommand)
	for nodeIP, serrs := range stdErrs.Out() {
		e.Ctx.Log.Warn("PostCommandFailed", "node %s, err: %v", nodeIP, serrs.String())
		pushAgentErrs[nodeIP] = errors.Errorf("Failed to push BKEAgent to node %s, PostCommandFailed err: %s", nodeIP, serrs.String())
	}

	return pushAgentErrs, nil
}

func (e *EnsureBKEAgent) pingAgent() error {
	ctx, c, bkeCluster, scheme, log := e.Ctx.Untie()

	// ping bkeagent，获取正确的agentStatus
	err, successNodesInfo, failedNodesInfo := phaseutil.PingBKEAgent(ctx, c, scheme, bkeCluster)
	if err != nil {
		log.Error(constant.BKEAgentNotReadyReason, "Failed to ping bkeagent err: %v", err)
		return err
	}

	for _, node := range failedNodesInfo {
		nodeIP := phaseutil.GetNodeIPFromCommandWaitResult(node)
		bkeCluster.SetNodeStateWithMessage(nodeIP, bkev1beta1.NodeInitFailed, fmt.Sprintf("Failed ping bkeagent"))
		// ping agent failed，unmask agentFlag
		bkeCluster.UnmarkNodeState(nodeIP, bkev1beta1.NodeAgentPushedFlag)
	}

	for _, node := range successNodesInfo {
		nodeIP := phaseutil.GetNodeIPFromCommandWaitResult(node)
		bkeCluster.SetNodeStateMessage(nodeIP, "BKEAgent is ready")
		// ping agent success，mask agentFlag
		bkeCluster.MarkNodeStateFlag(nodeIP, bkev1beta1.NodeAgentPushedFlag)
		bkeCluster.MarkNodeStateFlag(nodeIP, bkev1beta1.NodeAgentReadyFlag)
	}

	// ping的时候会采集节点的hostanme，此处需要进行校验nodes字段
	// validate nodes field
	if clusterutil.IsBKECluster(bkeCluster) {
		err = bkevalidate.ValidateNodesFields(bkeCluster.Spec.ClusterConfig.Nodes)
	}
	// bocloud 集群由于创建时就没有部分节点信息不能按照标准bke集群的方式校验
	if clusterutil.IsBocloudCluster(bkeCluster) {
		err = bkevalidate.ValidateNonStandardNodesFields(bkeCluster.Spec.ClusterConfig.Nodes)
	}

	if err != nil {
		errInfo := fmt.Sprintf("Failed to validate nodes fields, err: %v", err)
		condition.ConditionMark(bkeCluster, bkev1beta1.BKEConfigCondition, confv1beta1.ConditionFalse, constant.BKEConfigInvalidReason, errInfo)
		log.Error(constant.BKEAgentNotReadyReason, err.Error())

		if strings.Contains(err.Error(), "hostname is not unique") {
			errInfo = fmt.Sprintf("Some nodes have duplicate Hostnames, which is not allowed. Please modify the hostname on the target node or set it in the BKECluster resource. err: %v", err)
			condition.ConditionMark(bkeCluster, bkev1beta1.BKEConfigCondition, confv1beta1.ConditionFalse, constant.BKEConfigInvalidReason, errInfo)
			log.Error(constant.HostNameNotUniqueReason, errInfo)
			for _, node := range e.needPushNodes {
				log.Error(constant.HostNameNotUniqueReason, "IP: %s Hostname: %s", node.IP, node.Hostname)
				bkeCluster.UnmarkNodeState(node.IP, bkev1beta1.NodeAgentPushedFlag)
				bkeCluster.UnmarkNodeState(node.IP, bkev1beta1.NodeAgentReadyFlag)
				bkeCluster.SetNodeStateWithMessage(node.IP, bkev1beta1.NodeInitFailed, errInfo)
			}
		}
		if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster); err != nil {
			return errors.Wrap(err, "failed to sync status until complete")
		}
		return err
	}

	patchFunc := func(cluster *bkev1beta1.BKECluster) {
		cluster.Spec.ClusterConfig.Nodes = bkeCluster.Spec.ClusterConfig.Nodes
	}

	if err := mergecluster.SyncStatusUntilComplete(c, bkeCluster, patchFunc); err != nil {
		return err
	}

	if bkeCluster.Status.ClusterHealthState == bkev1beta1.Deploying && len(failedNodesInfo) > 0 {
		return errors.Errorf("At Deploying state, all nodes agent need ping success, retry later. failed nodes: %v", failedNodesInfo)
	}

	if len(successNodesInfo) == 0 {
		log.Error(constant.BKEAgentNotReadyReason, "Failed to ping all nodes bkeagent")
		return errors.Errorf("Failed to ping all nodes bkeagent")
	}

	//如果本次需要推送的agent 都ping不通需要返回错误
	if len(failedNodesInfo) > 0 {
		failedCount := 0
		for _, node := range e.needPushNodes {
			if utils.ContainsString(failedNodesInfo, node.IP) {
				failedCount++
			}
		}
		if failedCount == len(e.needPushNodes) {
			return errors.Errorf("None of the nodes that need to push the agent can be pinged, failed nodes: %v", failedNodesInfo)
		}
	}

	return nil
}
